#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
@Author          : ricsy
@File            : config_processor.py
@Create Time     : 2025/6/2 19:11
@Last Modified   :
@Description     :
"""
import json
import os
import re
from datetime import datetime
from pathlib import Path
from types import SimpleNamespace
from typing import Any, Dict, List

import tomli

from src.settings import logger, project_root


class ConfigNamespace(SimpleNamespace):
    """配置命名空间"""

    def __init__(self, config_dict: dict):
        super().__init__(**config_dict)

    def __repr__(self):
        return f"<Config {list(self.__dict__.keys())}>"

    def __setattr__(self, key, value):
        """防止意外修改的配置对象"""
        if key in self.__dict__:
            logger.warning(f"配置项 '{key}' 只读")
        super().__setattr__(key, value)


class ConfigProcessor:
    """
    动态配置处理器，支持变量插值和函数扩展
    1）基础变量替换： {year} → 2025	                        自动替换日期/作者等
    2）环境变量读取： {env('SECRET_KEY')}	                安全读取系统环境变量
    3）动态函数调用： {datetime.now().year}	                支持任意Python函数
    4）多级配置继承： {parent.child.value}	                支持嵌套配置引用
    5）类型自动转换： port = "{env('PORT', '8000')}" → int	自动识别数字/布尔值
    """

    def __init__(self, config_path: str = "config/config.toml"):
        self.raw_config = self._load_raw_config(project_root / config_path)
        self.context = {
            "datetime": datetime,
            "env": lambda key, default=None: self._get_env(key, default),
            "year": str(datetime.now().year),
        }

    @staticmethod
    def _load_raw_config(path: str) -> Dict[str, Any]:
        path = Path(path)
        if not path.exists():
            raise FileNotFoundError(f"Config file not found: {path}")
        with open(path, "rb") as f:
            return tomli.load(f)

    @staticmethod
    def _get_env(key: str, default=None) -> str:
        """获取环境变量"""
        return os.getenv(key, default)

    @staticmethod
    def _should_format(s: str) -> bool:
        """判断字符串是否需要格式化"""
        s = s.strip()

        # 情况1：完整JSON对象/数组，不进行格式化
        if (s.startswith("{") and s.endswith("}")) or (
            s.startswith("[") and s.endswith("]")
        ):
            try:
                json.loads(s)
                return False
            except Exception:
                # JSON 解析失败，可能是格式化字符串
                pass

        # 情况2：完整的<script>标签，不格式化
        if re.fullmatch(r"<\s*script[^>]*>.*<\s*/\s*script\s*>", s, re.DOTALL):
            return False

        # 检查是否包含格式化占位符
        if re.search(r"\{[^{}]+}", s):
            return True

        # 情况3：常见非格式化模式
        non_format_patterns = [
            r"^https?://\S+$",  # URL
            r"^\d+$",  # 纯数字
            r"^\s*\{\s*\}\s*$",  # 空对象
        ]
        return not any(re.fullmatch(p, s) for p in non_format_patterns)

    def _resolve_value(self, value: Any, parent_section: Dict[str, Any] = None) -> Any:
        """递归解析配置值"""
        if isinstance(value, str):
            if self._should_format(value):
                try:
                    # 合并全局上下文和父级配置
                    format_context = {**self.context}
                    if parent_section:
                        format_context.update(
                            {
                                k: v
                                for k, v in parent_section.items()
                                if not isinstance(v, (dict, list, tuple))
                            }
                        )
                    return value.format(**format_context)
                except KeyError:
                    # logger.warning("警告: 缺少格式化变量 {}, 原样返回字符串".format(e))
                    pass
            return value
        elif isinstance(value, dict):
            # 处理字典时传递自身作为父级上下文
            return {k: self._resolve_value(v, value) for k, v in value.items()}
        elif isinstance(value, (list, tuple)):
            return [self._resolve_value(i, parent_section) for i in value]
        return value

    def get_section(self, section: str) -> Dict[str, Any]:
        """获取处理后的配置节"""
        keys = section.split(".")
        current = self.raw_config

        try:
            for key in keys:
                if isinstance(current, dict):
                    current = current[key]
                else:
                    logger.warning(f"路径 '{section}' 在 '{key}' 处遇到非字典值")
            return self._resolve_value(current)
        except KeyError as e:
            logger.warning(f"配置路径 '{section}' 中不存在: {str(e)}")
            return {}

    def inject_to_context(self, section: str):
        """将配置注入上下文件"""
        _config = self.get_section(section)
        # 确保所有解析后的值都可用作上下文
        self.context.update(_config)

    def as_namespace(self, section: str) -> ConfigNamespace:
        """返回对象式配置访问器"""
        _config = self.get_section(section)
        return ConfigNamespace(_config)

    def get_ignore_patterns(self) -> List[str]:
        """获取需要忽略正则模式"""
        _config = self.get_section("build")
        return ConfigNamespace(_config).ignore_patterns

    def get_static_dir(self) -> Path | None:
        """获取静态文件目录"""
        resources_config = self.get_section("html.config.resources")
        html_static_path = ConfigNamespace(resources_config).html_static_path
        build_config = self.get_section("build")
        source_dir = build_config["source_dir"]
        return (
            project_root / source_dir / html_static_path[0]
            if len(html_static_path) > 0
            else None
        )


if __name__ == "__main__":
    config = ConfigProcessor()

    # 使用方式一：直接获取配置节
    base_config = config.get_section("default")
    print("[type=normal]", base_config["project"])

    # 使用方式二：对象式访问
    safe_cfg = config.as_namespace("default")
    print("[type=object]", safe_cfg.project)
    safe_cfg.project = "new"  # 触发警告

    print(config.get_ignore_patterns())
    print(config.get_static_dir())
